﻿using System;
using System.Collections.Generic;
using System.Text;

using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;

namespace Uniriotec.DC.RTSP
{
    public class Server
    {
        private Thread sendVideoThread;

        #region Private Variables

        #region RTP variables

        private Socket RTPsocket; //socket to be used to send and receive UDP packets
        private IPAddress ClientIPAddr; //Client IP address
        private int RTP_dest_port = 0; //destination port for RTP packets  (given by the RTSP Client)
        private bool skipToFrame = false; // stores if the client asked for a specific start point

        #endregion

        #region Video Variables

        private int imagenb = 0; //image nb of the image currently transmitted
        private VideoStream video; //VideoStream object used to access video frames

        private int FRAME_PERIOD = 100; //Frame period of the video to stream, in ms
        private int VIDEO_LENGTH = 500; //length of the video in frames

        private byte[] buf; //buffer used to store the images to send to the client

        #endregion
        
        #region RTSP Variables

        private Socket RTSPClient; //socket used to send/receive RTSP (TCP) messages
        //input and output stream filters
        private BufferedReader<NetworkStream> RTSPBufferedReader;
        private BufferedWriter<NetworkStream> RTSPBufferedWriter;
        private String videoFileName; //video file requested from the client
        private RtspStates connectionState = RtspStates.STOPED;
        private static int RTSP_ID; //ID of the RTSP session
        private int RTSPSeqNb = 0; //Sequence number of RTSP messages within the session
        private const string CRLF = "\n";

        #endregion

        #region Server Configuration Variables

        private string baseVideoPath = String.Empty;
        private int serverPort = 554;

        #endregion

        #endregion

        #region Events

        public event EventHandler RTSPActionReceived;
        public event EventHandler ConnectionStateChanged;
        public event EventHandler FrameSent;

        #endregion

        #region Properties

        public int ServerPort
        {
            get { return serverPort; }
            set { serverPort = value; }
        }

        public String VideoFileName
        {
            get { return videoFileName; }
        }

        public string BaseVideoPath
        {
            get { return baseVideoPath; }
            set { baseVideoPath = value; }
        }

        public bool ClientConnected
        {
            get { return !(RtspConnectionState == RtspStates.DISCONNECTED || RtspConnectionState == RtspStates.STOPED); }
        }

        public RtspStates RtspConnectionState
        {
            get
            {
                return connectionState;
            }
            set
            {
                connectionState = value;

                #region raise ConnectionStateChanged Event

                try
                {
                    EventHandler handler = ConnectionStateChanged;

                    if (handler != null)
                    {
                        handler(this, null);
                    }
                }
                catch
                {
                    // Handle exceptions here
                }

                #endregion

            }
        }

        #endregion

        public void ForceStop()
        {
            //stop sending and exit
            if (!(sendVideoThread == null || sendVideoThread.ThreadState == ThreadState.Aborted))
                sendVideoThread.Abort();
        }



        #region Constructor

        public Server()
        {
            //allocate memory for the sending buffer
            buf = new byte[15000];
        }

        #endregion

        public void StartListening()
        {
            System.Threading.ThreadStart b = new System.Threading.ThreadStart(InitializeServer);
            System.Threading.Thread a = new System.Threading.Thread(b);
            a.Name = "RTP Server TCP Listener";
            a.Start();
        }


        //------------------------------------
        //main
        //------------------------------------
        public void InitializeServer()
        {
            sendVideoThread = new Thread(new ThreadStart(backgroundWorker1_DoWork));
        }

        public void WaitForConnection()
        {

            //Initiate TCP connection with the client for the RTSP session
            TcpListener listenSocket = new TcpListener(IPAddress.Any, serverPort);
            listenSocket.Start();
            RTSPClient = listenSocket.AcceptSocket();

            listenSocket.Stop();

            //Get Client IP address 
            ClientIPAddr = ((IPEndPoint)RTSPClient.RemoteEndPoint).Address;

            //Set input and output stream filters:
            RTSPBufferedReader = new BufferedReader<NetworkStream>(RTSPClient);
            RTSPBufferedWriter = new BufferedWriter<NetworkStream>(RTSPClient);

            Random a = new Random();
            RTSP_ID = a.Next(10000000, 99999999);
        }

        public void PrepareToSendVideo()
        {
            PrepareToSendVideo(this.VideoFileName);
        }

        public void PrepareToSendVideo(string videoFileName)
        {
            //init the VideoStream object:
            video = new VideoStream(videoFileName);

            //init RTP socket
            RTPsocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        }

        public void PauseSendingVideo()
        {
            //stop sending the video
            sendVideoThread.Suspend();
        }

        public void StartSendingVideo()
        {
            //start sending video
            if (sendVideoThread.ThreadState == ThreadState.Unstarted)
                sendVideoThread.Start();
            else
                sendVideoThread.Resume();
        }

        public void EndConnection()
        {
            try
            {
                //stop sending
                if (sendVideoThread.ThreadState == ThreadState.Suspended)
                    sendVideoThread.Resume();
                sendVideoThread.Abort();
            }
            catch (Exception e)
            { }
            finally
            {
                imagenb = 0;

                //close sockets
                RTSPClient.Close();
                RTPsocket.Close();
            }


        }

        //------------------------------------
        //Parse RTSP Request
        //------------------------------------
        public RTSPAction parse_RTSP_request()
        {
            RTSPAction request_type = RTSPAction.UNKNOWN;
            try
            {

                //parse request line and extract the request_type:
                String RequestLine = RTSPBufferedReader.ReadLine();

                string[] tokens = RequestLine.Split(' ');
                int i = 0;
                String request_type_string = tokens[i];

                //convert to request_type structure:
                if (request_type_string == "SETUP")
                    request_type = RTSPAction.SETUP;
                else if (request_type_string == "PLAY")
                    request_type = RTSPAction.PLAY;
                else if (request_type_string == "PAUSE")
                    request_type = RTSPAction.PAUSE;
                else if (request_type_string == "TEARDOWN")
                    request_type = RTSPAction.TEARDOWN;

                if (request_type == RTSPAction.SETUP)
                {
                    //extract VideoFileName from RequestLine
                    videoFileName = baseVideoPath + @"\" + (tokens[++i].Split('/'))[3];
                }

                //parse the SeqNumLine and extract CSeq field
                String SeqNumLine = RTSPBufferedReader.ReadLine();
                tokens = SeqNumLine.Split(' ');
                i = 1;

                RTSPSeqNb = int.Parse(tokens[i]);


                //get LastLine
                String LastLine = RTSPBufferedReader.ReadLine();

                if (request_type == RTSPAction.SETUP)
                {
                    //extract RTP_dest_port from LastLine
                    tokens = LastLine.Split(' ');
                    RTP_dest_port = int.Parse(tokens[3]);
                }
                //else LastLine will be the SessionId line ... Ignore

                string timeRangeLine = RTSPBufferedReader.ReadLine();

                if (timeRangeLine != String.Empty)
                {
                    timeRangeLine = timeRangeLine.Split('=')[1];
                    timeRangeLine = timeRangeLine.Substring(0, timeRangeLine.Length - 1);
                    skipToFrame = true;

                    imagenb = Convert.ToInt32(timeRangeLine) * 10;
                    RTSPBufferedReader.ReadLine(); //throw final \n out
                }

            }
            catch (Exception ex)
            {
                throw ex;
            }

            #region raise RTPActionReceived Event

            try
            {
                EventHandler handler = RTSPActionReceived;
                
                if (handler != null)
                {
                    handler(this, null);
                }
            }
            catch
            {
                // Handle exceptions here
            }

            #endregion

            return (request_type);
        }

        //------------------------------------
        //Send RTSP Response
        //------------------------------------
        public void send_RTSP_response(int statusCode)
        {
            try
            {
                string response;
                response = "RTSP/1.0 " + GetStatusCodeMessage(statusCode) + CRLF;
                RTSPBufferedWriter.Write(response);

                response = "CSeq: " + RTSPSeqNb + CRLF;
                RTSPBufferedWriter.Write(response);

                response = "Session: " + RTSP_ID + CRLF + "\n";
                RTSPBufferedWriter.Write(response);

                RTSPBufferedWriter.Flush();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public string GetStatusCodeMessage(int statusCode)
        {
            switch (statusCode)
            {
                case 100: return "100 Continue";
                case 200: return "200 OK";
                case 201: return "201 Created";
                case 250: return "250 Low on Storage Space";
                case 300: return "300 Multiple Choices";
                case 301: return "301 Moved Permanently";
                case 302: return "302 Moved Temporarily";
                case 303: return "303 See Other";
                case 304: return "304 Not Modified";
                case 305: return "305 Use Proxy";
                case 400: return "400 Bad Request";
                case 401: return "401 Unauthorized";
                case 402: return "402 Payment Required";
                case 403: return "403 Forbidden";
                case 404: return "404 Not Found";
                case 405: return "405 Method Not Allowed";
                case 406: return "406 Not Acceptable";
                case 407: return "407 Proxy Authentication Required";
                case 408: return "408 Request Time-out";
                case 410: return "410 Gone";
                case 411: return "411 Length Required";
                case 412: return "412 Precondition Failed";
                case 413: return "413 Request Entity Too Large";
                case 414: return "414 Request-URI Too Large";
                case 415: return "415 Unsupported Media Type";
                case 451: return "451 Parameter Not Understood";
                case 452: return "452 Conference Not Found";
                case 453: return "453 Not Enough Bandwidth";
                case 454: return "454 Session Not Found";
                case 455: return "455 Method Not Valid in This State";
                case 456: return "456 Header Field Not Valid for Resource";
                case 457: return "457 Invalid Range";
                case 458: return "458 Parameter Is Read-Only";
                case 459: return "459 Aggregate operation not allowed";
                case 460: return "460 Only aggregate operation allowed";
                case 461: return "461 Unsupported transport";
                case 462: return "462 Destination unreachable";
                case 500: return "500 Internal Server Error";
                case 501: return "501 Not Implemented";
                case 502: return "502 Bad Gateway";
                case 503: return "503 Service Unavailable";
                case 504: return "504 Gateway Time-out";
                case 505: return "505 RTSP Version not supported";
                case 551: return "551 Option not supported";
                default: return String.Empty;
            }

        }


        private bool SendVideo()
        {
            if (skipToFrame)
            {
                skipToFrame = false;
                video.SkipToFrame(imagenb);
            }

            //if the current image nb is less than the length of the video
            if (imagenb < VIDEO_LENGTH)
            {
                //update current imagenb
                imagenb++;

                try
                {
                    //get next frame to send from the video, as well as its size
                    int image_length = video.GetNextFrame(buf);

                    //Builds an RTPpacket object containing the frame
                    RTPpacket rtp_packet = new RTPpacket((int)RTPPayloadType.MJPEG, imagenb, imagenb * FRAME_PERIOD, buf, image_length);

                    //get to total length of the full rtp packet to send
                    int packet_length = rtp_packet.getlength();

                    //retrieve the packet bitstream and store it in an array of bytes
                    byte[] packet_bits = new byte[packet_length];
                    rtp_packet.getpacket(packet_bits);

                    //send the packet as a DatagramPacket over the UDP socket 
                    IPEndPoint client = new IPEndPoint(ClientIPAddr, RTP_dest_port);

                    RTPsocket.SendTo(packet_bits, packet_length, SocketFlags.None, client);

                    //Console.Out.WriteLine("Send frame #"+imagenb);
                    //print the header bitstream
                    rtp_packet.printheader();

                    return true;
                }
                catch (Exception ex)
                {
                    if (RtspConnectionState != RtspStates.PLAYING)
                        return false;
                    throw ex;
                }
            }
            else
            {
                //if we have reached the end of the video file, stop sending
            }
            return false;

        }


        private void backgroundWorker1_DoWork()
        {
            while (SendVideo() && RtspConnectionState == RtspStates.PLAYING)
            {
                Thread.Sleep(50);
            }

            backgroundWorker1_RunWorkerCompleted();
        }

        private void backgroundWorker1_RunWorkerCompleted()
        {
            RtspConnectionState = RtspStates.INIT;
            imagenb = 0;
        }

    }


}

